Zydecx's Site

Debug code, debug life, debug today!

Class对象(Java)

Time: , by zydecx

关于Class对象,需要了解的是:

  • 每个类都有一个Class对象
  • 默认情况下,类通过“原生类加载器”从磁盘中加载;可以挂接其他的类加载器
  • Java采用动态加载机制;类在第一次被访问静态成员时被加载,从这个角度上讲,可以认为构造器为类的静态方法

获得Class对象

获得Class对象的方法有:

  • Class类的静态方法forNameClass.forName("full-qualified-class-name")
  • 对象的getClass()方法,该方法属于Object
  • 类字面常量classClassName.class
    • 对于基本类型,有boolean.class <=> Boolean.TYPE,但boolean.class != Boolean.class

以上方法获得的Class对象是等价的,即

String s = "string";
Class stringClass1 = s.getClass();
Class stringClass2 = Class.forName("java.lang.String");
Class stringClass3 = String.class;

stringClass1 == stringClass2;   // =true
stringClass1 == stringClass3;   // =true

Class对象的几个方法

  • getFields()/getDeclaredFields()

    getFields()获得类的所有public数据成员,包括父类的;
    getDeclaredFields()获得类的所有数据成员(public,protected,private,default),不包括父类。

    其他getXxx()/getDeclaredXxx()逻辑与此类似。

  • newInstance()

    创建实例,返回Object类型。

泛化的Class引用

Class引用支持泛型引用(Java SE5+),如Class<Integer> intClass;

  • newInstance()返回确定类型

    不使用泛型引用,newInstance()方法返回Object类型对象;使用后可返回确定类型,如

    Class<Integer> intClass = Integer.class;
    Class normalClass = Integer.class;
    
    Integer intValue = intClass.newInstance();
    Object normalValue = normalClass.newInstance();
  • ?通配符

    ?通配符可解决具有继承关系的Class引用,如下例所示,norNumClass不能引用int.class,相反,numClass却可以。

    Class<?> normalClass;
    normalClass = String.class;
    normalClass = HashMap.class; 
    
    Class<? extends Number> numClass;
    numClass = int.class;
    numClass = Number.class;
    
    Class<Number> norNumClass = Number.class;
  • getSupperclass()返回父类泛化类型

    虽然,可以在编译时确定一个类的父类,但使用getSupperclass()获得父类Class引用时,只能使用下面的方式:

    Class<? super String> stringSuper = String.class.getSupperclass();

    特别地,

    • 对于Object/接口/基本类型/void,返回null
    • 对于Object,引用方式需改为Class<Object> objectSuper = Object.class.getSupperclass();

初始化(深入研究)

之前曾经讨论过创建对象时的初始化顺序,这里将进一步研究。

类的初始化

类的加载和初始化顺序为:

  1. 加载

    由类加载器执行,从磁盘(或其他)中查找字节码,创建Class对象。

  2. 链接

    验证字节码,为静态域分配存储空间。该阶段将初始化编译时常量(static final修饰,且值为常量)。如果必须的话,解析对其他类的引用。

  3. 初始化

    先初始化父类静态变量和初始化块,再初始化本类的静态变量和初始化块。

对于类的初始化,有以下结论:

  • 引用类的字面常量class或编译时常量,仅执行加载和链接,不初始化
  • 引用Class.forName()或类的静态方法或非常数静态域时,先执行加载和链接,再执行初始化

对象的初始化

创建对象时,首先执行类的初始化,即类的加载、链接和初始化。之后,执行对象的初始化,包括:

  1. 在堆上为对象分配存储空间
  2. 将存储空间清零,基本类型数据设为默认值,引用类型设为null
  3. 执行非静态对象(包括非静态域和初始化块)初始化
  4. 执行构造器

当存在继承关系时,1、2步将同时完成父类和子类的存储空间分配和清零,之后先执行父类的非静态对象初始化和构造器,再执行子类的非静态对象初始化和构造器

示例(类的初始化)

逐句执行ClassInitialization.main()中的每一条语句,观察相应的输出,来理解类的初始化。

public class BaseClass {
    public static final String aaToExtend = "base aa";
    public static final String aa = "aa";
    public static String bb = "bb";
    
    static {
        System.out.println("base static init");
    }
    
}


public class ExtendClass extends BaseClass {
    public static final String aaToExtend = "extend aa";
    public static final String cc = "cc";
    public static String dd = "dd";
    
    static {
        System.out.println("extend static init");
    }
}

public class ClassInitialization {
    public static void main(String[] arg) throws ClassNotFoundException {
    //  System.out.println(BaseClass.aaToExtend);   //base aa
    //  System.out.println(BaseClass.aa);   //aa
    //  System.out.println(BaseClass.bb);   //base static init\nbb
    //  Class<BaseClass> c = BaseClass.class;   //
    //  Class c = Class.forName("BaseClass");   //base static init

    //  System.out.println(ExtendClass.aaToExtend); //extend aa
    //  System.out.println(ExtendClass.cc); //cc
    //  System.out.println(ExtendClass.dd); //base static init\nextend static init\ndd
    //  Class<ExtendClass> c = ExtendClass.class;   //
    //  Class c = Class.forName("ExtendClass"); //base static init\nextend static init
        
    }
}

This is a magic phrase. You CANNOT see it(I'll really FULE you if you do that), but it does work. Why? You may feel confused. OK, at least it doesn't afftect your experience and it works. That is what we call MAGICE!